feat: Obfuscate variables in plugin - #2755
Conversation
|
pkg.pr.new packages benchmark commit |
Bundle size comparison (
|
| 🟢 Decreased | ➖ Unchanged | 🔴 Increased (max 0.02%) | ❔ Unknown |
|---|---|---|---|
| 0 | 301 | 21 | 0 |
import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):
| Test | tsdown |
|---|---|
| tgpu_init.ts | 264.54 kB ( |
| tgpu_initFromDevice.ts | 264.01 kB ( |
| tgpu_resolve.ts | 167.61 kB ( |
| tgpu_resolveWithContext.ts | 167.54 kB ( |
| tgpu_bindGroupLayout.ts | 71.83 kB ( |
| tgpu_mutableAccessor.ts | 68.22 kB ( |
| tgpu_accessor.ts | 68.22 kB ( |
| tgpu_privateVar.ts | 66.96 kB ( |
| tgpu_workgroupVar.ts | 66.96 kB ( |
| tgpu_const.ts | 66.44 kB ( |
| tgpu_lazy.ts | 66.32 kB ( |
| tgpu_fn.ts | 38.75 kB ( |
| tgpu_fragmentFn.ts | 38.75 kB ( |
| tgpu_vertexFn.ts | 38.57 kB ( |
| tgpu_computeFn.ts | 38.27 kB ( |
| tgpu_vertexLayout.ts | 27.39 kB ( |
| tgpu_comptime.ts | 15.07 kB ( |
| tgpu_unroll.ts | 1.66 kB ( |
| tgpu_slot.ts | 1.54 kB ( |
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.
Resolution Time Benchmark---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.92, 1.88, 4.34, 6.92, 7.93, 11.17, 22.55, 23.12]
line [0.96, 1.95, 4.18, 6.65, 7.14, 12.87, 21.63, 23.60]
line [1.00, 1.96, 4.53, 6.83, 7.62, 11.23, 21.19, 24.33]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.33, 0.49, 0.66, 0.81, 1.17, 1.23, 1.46, 1.61]
line [0.34, 0.53, 0.72, 0.84, 1.10, 1.14, 1.46, 1.53]
line [0.35, 0.58, 0.75, 0.84, 1.16, 1.26, 1.51, 1.61]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.78, 2.11, 4.22, 6.72, 11.87, 24.38, 53.93, 111.53]
line [0.75, 1.94, 4.03, 6.64, 12.23, 24.86, 53.22, 108.45]
line [0.82, 2.12, 3.93, 6.43, 12.45, 25.96, 54.59, 113.68]
|
| declaredNames: string[]; | ||
| }; | ||
|
|
||
| export type Externals = Set<string>; |
There was a problem hiding this comment.
Less work swapping it for a map here than in unplugin, and it needs to become a map eventually
|
@pullfrog review |
There was a problem hiding this comment.
Important
The externalNames iteration in the obfuscator swaps the map's key and value. This works today only because tinyest-for-wgsl stores identical strings for both; it will silently produce incorrect WGSL if that invariant ever changes.
Reviewed changes — PR #2755 adds an experimental plugin-time obfuscation pass that renames parameters, local variables, and external bindings in the transpiled tinyest AST, plus the EXPERIMENTAL_obfuscate option across the Babel/Rollup/Bun plugin entry points, a new docs page, and supporting test updates.
- Added
packages/unplugin-typegpu/src/core/obfuscate.ts— AST-to-AST obfuscation ofparams,body, andexternalNames. - Changed
ExternalsfromSet<string>toMap<string, string>inpackages/tinyest-for-wgsl/src/types.tsand updated all consumers/test snapshots. - Wired
EXPERIMENTAL_obfuscateintounplugin-typegpuplugin options, with a guard that rejects enabling it alongsideautoNamingEnabled. - Added docs entry (
apps/typegpu-docs/.../minifying-shaders.mdx) and sidebar link. - Updated/added tests in
packages/tinyest-for-wgsl/tests/parsers.test.ts,packages/unplugin-typegpu/test/obfuscation.test.ts, and severaltypegpusnapshot tests.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
|
@pullfrog review |
There was a problem hiding this comment.
ℹ️ No critical issues — minor suggestions inline.
Reviewed changes
This PR introduces an EXPERIMENTAL_obfuscate option for unplugin-typegpu that renames TGSL parameters, local variables, and external identifiers to short generated names before WGSL code generation. It also adds the supporting infrastructure: changing externalNames from a Set to a Map to carry original accessor chains, adding validation that prevents obfuscation and auto-naming from being enabled together, updating metadata serialization in the Babel and unplugin factories, and adding documentation and tests.
- Added
packages/unplugin-typegpu/src/core/obfuscate.tswith a tinyest-to-tinyest AST transformer that renames identifiers. - Added
EXPERIMENTAL_obfuscateplugin option with a conflict check againstautoNamingEnabled. - Changed
ExternalsfromSet<string>toMap<string, string>so obfuscated keys can map back to original accessor chains. - Updated
packages/typegpu/src/resolutionCtx.tsto resolveundefined/Infinity/NaNexternals correctly. - Added
packages/unplugin-typegpu/test/obfuscation.test.tscovering AST-level obfuscation for variables, parameters, externals, destructuring, shadowing, and name exhaustion. - Added docs page for shader minification/obfuscation.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Pull request overview
Adds an experimental shader obfuscation/minification pathway by obfuscating identifiers captured in unplugin-typegpu metadata, supported by updates to the tinyest transpiler externals representation and expanded test/docs coverage.
Changes:
- Introduces
EXPERIMENTAL_obfuscateoption to obfuscate parameters/locals/externals stored in plugin metadata. - Changes tinyest-for-wgsl “externals” from
Set<string>toMap<string, string>to support renaming/aliasing of external chains. - Adds/updates tests and documentation for obfuscation behavior and collision/shadowing scenarios.
Reviewed changes
Copilot reviewed 15 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/unplugin-typegpu/test/obfuscation.test.ts | New plugin + core obfuscation test coverage and option-conflict checks. |
| packages/unplugin-typegpu/src/core/obfuscate.ts | New obfuscation pass over tinyest AST + externals mapping. |
| packages/unplugin-typegpu/src/core/common.ts | Plumbs EXPERIMENTAL_obfuscate through transpilation and adds option validation. |
| packages/unplugin-typegpu/src/core/factory.ts | Updates externals serialization and validates options in unplugin factory. |
| packages/unplugin-typegpu/src/babel.ts | Updates Babel plugin option validation and externals emission. |
| packages/unplugin-typegpu/src/bun.ts | Validates options for Bun plugin entrypoint. |
| packages/typegpu/src/resolutionCtx.ts | Adjusts external lookup to distinguish “missing” vs “present-but-undefined”. |
| packages/typegpu/src/internal.ts | Exposes stringifyNode via internal exports for tests/tooling. |
| packages/typegpu/tests/tgslFn.test.ts | Adds regression coverage for std shadowing behavior. |
| packages/typegpu/tests/tgsl/wgslGenerator.test.ts | Snapshot formatting updates. |
| packages/typegpu/tests/std/boolean/not.test.ts | Snapshot formatting updates. |
| packages/typegpu/tests/mutabilityTracking.test.ts | Snapshot formatting updates. |
| packages/tinyest-for-wgsl/src/types.ts | Switches Externals to Map<string, string>. |
| packages/tinyest-for-wgsl/src/parsers.ts | Emits externals into a Map instead of a Set. |
| packages/tinyest-for-wgsl/tests/helpers.ts | New shared helpers for dual-parser tests. |
| packages/tinyest-for-wgsl/tests/parsers.test.ts | Updates expectations for externals now being a Map. |
| apps/typegpu-docs/src/content/docs/advanced/minifying-shaders.mdx | New documentation page for obfuscation/minification. |
| apps/typegpu-docs/astro.config.mjs | Adds docs nav entry for the new page. |
Suppressed comments (1)
packages/tinyest-for-wgsl/src/types.ts:15
- The
Context.externalNamesdoc still says it “Holds a set…”, butExternalsis now aMap<string, string>. Updating the comment will prevent confusion for future maintainers.
export type Context = {
/** Holds a set of all identifiers that were used in code, but were not declared in code. */
externalNames: Externals;
/** Used to signal to identifiers that they should not treat their resolution as possible external uses. */
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Important
The obfuscation name generator can emit WGSL keywords, which will produce invalid shader code once enough identifiers are present. See the inline comment on packages/unplugin-typegpu/src/core/obfuscate.ts.
Reviewed changes
- Added an
EXPERIMENTAL_obfuscateplugin option that renames parameters, variables, and captured externals to short lowercase identifiers at build time. - Changed
tinyest-for-wgslexternalNamesfromSet<string>toMap<string, string>so obfuscated AST keys can still resolve to original external access chains at runtime. - Updated
resolutionCtx.tsto resolve externals by map membership (id in externalMap) rather than by truthiness, preserving null/undefined external values. - Added
stringifyNodeto the internal TypeGPU exports for test assertions. - Added docs page and parser/unplugin tests.
ℹ️ Nitpicks
- The options reference in
apps/typegpu-docs/src/content/docs/tooling/unplugin-typegpu.mdxwas not updated to listEXPERIMENTAL_obfuscate; consider adding it there (marked experimental) to keep the two docs pages consistent.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — one prior suggestion is still outstanding.
Reviewed changes
Reviewed the two new commits on PR #2755 since the prior Pullfrog review.
- Changed
resolutionCtx.tsto useObject.prototype.hasOwnProperty.call(layer.externalMap, id)instead ofid in layer.externalMap, so externals that are present-but-null/undefinedare resolved and prototype-named keys are no longer mistaken for externals. - Added
packages/typegpu/tests/internal/metadata.test.ts(replacingmetadataNormalization.test.ts) covering metadata normalization, deferred externals for v1 and v2, variable recapture, and invalid-identifier renaming. - Added parser coverage in
packages/tinyest-for-wgsl/tests/parsers.test.tsforundefined/Infinity/NaNbeing treated as external identifiers.
ℹ️ Outstanding prior suggestion
The earlier Pullfrog suggestion to add an end-to-end test that runs tgpu.resolve on an obfuscated function remains open. It's a minor follow-up rather than a merge blocker, but worth tracking if integration coverage for obfuscated WGSL output is planned.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the incremental delta on PR #2755 since the prior Pullfrog review.
- Merged
origin/maininto the feature branch; the only net change was restoring thedualImplexport inpackages/typegpu/src/internal.ts, matchingmain.
Kimi K2 (free via Pullfrog for OSS) | 𝕏

Blocked by #2788
We obfuscate variables, externals and parameters. Struct props are left as-is (at least for now).
Testing:
Further work: #2783, #2786.
Also, helpers like
fullScreenTrianglewill remain as is, they are implemented in raw wgsl.